package com.mojn.jmx;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsyncClient;
import com.blacklocus.metrics.CloudWatchReporter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.JmxAttributeGauge;
import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.*;
import javax.management.openmbean.CompositeData;
import javax.management.relation.MBeanServerNotificationFilter;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class ReporterAgent implements NotificationListener {
protected MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
protected MetricRegistry metricRegistry = new MetricRegistry();
protected Settings settings;
protected String metricsSuffix = "";
Logger log = null;
static class Settings {
public String awsSecretKey = "";
public String awsAccessKey = "";
public String instanceId;
public String endPoint = "monitoring.eu-west-1.amazonaws.com";
// hand parsed json to support Composite Beans
public Map<ObjectName, ObjectNode> beans;
public String cloudWatchNamespace = "jmx";
public long reportInterval = TimeUnit.MINUTES.toSeconds(1);
public boolean logIgnoredAttributes = false;
public Map<String,String> simpleLogger = Collections.emptyMap();
}
ObjectMapper jsonMapper = new ObjectMapper();
public void init(String agentArguments) throws IOException {
String configFile = agentArguments != null ? agentArguments : "jmx-cloudwatch.json";
readConfig(configFile);
initLogger();
initAllowedBeans();
AWSCredentials awsCredentials = new BasicAWSCredentials(settings.awsAccessKey, settings.awsSecretKey);
//set up listeners for jmx events, so we get notifications for all new beans registered (premain runs "pre main"
addBeanNotificationListener();
registerPlatformBeans();
initReporter(awsCredentials);
}
protected void initLogger() {
Map<String,String> existingLoggerProps = null;
if (settings.simpleLogger.size()>0) {
existingLoggerProps = new HashMap<String,String>(settings.simpleLogger.size());
for (Map.Entry<Object, Object> sysProp : System.getProperties().entrySet()) {
if (sysProp.getKey().toString().startsWith("org.slf4j.simpleLogger.")) {
existingLoggerProps.put(sysProp.getKey().toString(), sysProp.getValue().toString());
}
}
for (Map.Entry<String, String> entry: settings.simpleLogger.entrySet()) {
System.setProperty("org.slf4j.simpleLogger." + entry.getKey(), entry.getValue());
}
}
log = LoggerFactory.getLogger(ReporterAgent.class);
if (existingLoggerProps!=null ) {
System.getProperties().putAll(existingLoggerProps);
}
}
protected void registerPlatformBeans() {
// list all domains :
Set<String> platformDomains = new HashSet<String>(Arrays.asList(mBeanServer.getDomains()));
for (Map.Entry<ObjectName, ObjectNode> beanEntry : settings.beans.entrySet()) {
// check if mBean is registered
MBeanInfo mBeanInfo = null;
ObjectName objectName = beanEntry.getKey();
if (!platformDomains.contains(objectName.getDomain())) {
continue;
}
try {
mBeanInfo = mBeanServer.getMBeanInfo(objectName);
} catch (Exception e) {
log.warn("Exception while looking up mbean name {} - {}",objectName.toString(),e.getMessage());
continue;
}
handleAttributes(mBeanInfo, objectName, beanEntry.getValue());
}
if (settings.logIgnoredAttributes) {
Set<ObjectName> objectNames = mBeanServer.queryNames(null, null);
for (ObjectName objectName : objectNames) {
ObjectNode jsonNodes = settings.beans.get(objectName);
if (jsonNodes==null) {
try {
logAllAttributes(objectName, mBeanServer.getMBeanInfo(objectName));
} catch (JMException e) {
log.warn("",e);
}
}
}
}
}
protected void handleAttributes(MBeanInfo mBeanInfo, ObjectName objectName, ObjectNode attributesToMonitor) {
for (MBeanAttributeInfo attr : mBeanInfo.getAttributes()) {
// check if we are interested in this attribute
String attrName = attr.getName();
JsonNode jsonNode = attributesToMonitor.get(attrName);
if (jsonNode != null) {
if (!attr.isReadable()) {
log.warn("cannot monitor unreadable attribute {}@{}", objectName.toString(), attrName);
continue;
}
if (jsonNode.isTextual()) {
String monitorName = jsonNode.asText() + metricsSuffix;
metricRegistry.register(monitorName, new JmxAttributeGauge(mBeanServer, objectName, attrName));
log.info("attribute {}@{} registered as {}", objectName , attrName, monitorName);
} else if (jsonNode.isObject()) {
if (!CompositeData.class.getName().equals(attr.getType())) {
log.warn("attribute {}@{} is not CompositeData", objectName, attr);
continue;
}
try {
CompositeData attribute = (CompositeData) mBeanServer.getAttribute(objectName, attrName);
handleComposite(objectName, attrName, attribute, (ObjectNode) jsonNode);
} catch (JMException e) {
log.warn("unable to get composite attributes {}@{}",objectName,attrName,e);
}
}
} else if (settings.logIgnoredAttributes) {
log.info("{}@{} ignored",objectName,attr.getName());
}
}
}
class CompositeGauge implements Gauge<Object> {
private final ObjectName objectName;
private final String attrName;
private final String compositeAttr;
CompositeGauge(ObjectName objectName, String attrName, String compositeAttr) {
this.objectName = objectName;
this.attrName = attrName;
this.compositeAttr = compositeAttr;
}
@Override
public Object getValue() {
Object value = null;
try {
CompositeData compositeData = (CompositeData) mBeanServer.getAttribute(objectName, attrName);
value = compositeData.get(compositeAttr);
} catch (Exception e) {
log.warn("error while getting {}@{}>{}", objectName, attrName, compositeAttr, e);
}
return value;
}
}
protected void handleComposite(final ObjectName objectName, final String attributeName, CompositeData composite, ObjectNode jsonNode) {
for (Iterator<String> iterator = jsonNode.fieldNames(); iterator.hasNext(); ) {
String compositeAttr = iterator.next();
try {
Object o = composite.get(compositeAttr);
// register handler based on Type
String monitorName = jsonNode.get(compositeAttr).textValue() + metricsSuffix;
metricRegistry.register(monitorName, new CompositeGauge(objectName, attributeName, compositeAttr));
log.info("mBean {}@{}>{} registered as {}", objectName.toString(), attributeName, compositeAttr, monitorName);
} catch (Exception e) {
log.warn("Attribute {} not part of CompositeData {}@{}", compositeAttr, objectName.toString(), attributeName);
}
}
}
protected void initReporter(AWSCredentials awsCredentials) {
final AmazonCloudWatchAsyncClient cloudWatchClient = new AmazonCloudWatchAsyncClient(awsCredentials);
cloudWatchClient.setEndpoint(settings.endPoint);
//start cloudwatch reporting
new CloudWatchReporter(
metricRegistry,
settings.cloudWatchNamespace,
cloudWatchClient
).start(settings.reportInterval, TimeUnit.SECONDS);
}
protected void addBeanNotificationListener() throws IOException {
MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
filter.enableAllObjectNames();
try {
mBeanServer.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this, filter, null);
} catch (InstanceNotFoundException e) {
throw new IOException(e);
}
}
protected void initAllowedBeans() {
metricsSuffix = settings.instanceId != null ? " instance=" + settings.instanceId + "*" : "";
}
protected void readConfig(String configFile) throws IOException {
File file = new File(configFile);
if (!file.isFile()) {
throw new IOException(String.format("%s not found", file.getAbsolutePath()));
}
settings = jsonMapper.readValue(file, Settings.class);
}
@Override
public void handleNotification(Notification notification, Object handback) {
try {
MBeanServerNotification mbs = (MBeanServerNotification) notification;
if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(mbs.getType())) {
ObjectName objectName = ((MBeanServerNotification) notification).getMBeanName();
ObjectNode jsonNodes = settings.beans.get(objectName);
MBeanInfo mBeanInfo = mBeanServer.getMBeanInfo(objectName);
if (jsonNodes != null) {
handleAttributes(mBeanInfo, objectName, jsonNodes);
} else if (settings.logIgnoredAttributes) {
logAllAttributes(objectName,mBeanInfo);
}
}
} catch (Exception exc) {
log.error("error while registering bean:", exc);
}
}
protected void logAllAttributes(ObjectName objectName, MBeanInfo mBeanInfo) {
for (MBeanAttributeInfo mBeanAttributeInfo : mBeanInfo.getAttributes()) {
log.info("{}@{} ignored",objectName,mBeanAttributeInfo.getName());
}
}
}